package com.wgo.precise.server.export;

import java.util.HashMap;
import java.util.Map;

import org.apache.log4j.Logger;

import remato.common.domain.helpers.ProjectParentable;
import remato.common.domain.property.PropertyTypeEnumImpl;
import remato.domain.common.Category;
import remato.domain.common.CategoryParentable;
import remato.domain.common.Project;
import remato.domain.common.PropertyDef;
import remato.domain.common.PropertySet;
import remato.domain.common.PropertyValue;
import remato.domain.common.Requirement;
import remato.domain.common.RequirementSystem;
import remato.domain.common.Stakeholder;
import remato.domain.common.impl.CategoryImpl;
import remato.domain.common.impl.ProjectImpl;
import remato.domain.common.impl.PropertyDefImpl;
import remato.domain.common.impl.PropertySetImpl;
import remato.domain.common.impl.PropertyValueImpl;
import remato.domain.common.impl.RequirementImpl;
import remato.domain.common.impl.RequirementSystemImpl;
import remato.domain.common.impl.StakeholderImpl;

import com.wgo.bpot.server.persist.PersistService;
import com.wgo.bpot.common.transport.util.DomainModelTypeNamingConvention;
import com.wgo.precise.server.preciseData.CategoryType;
import com.wgo.precise.server.preciseData.ProjectType;
import com.wgo.precise.server.preciseData.PropertyDefType;
import com.wgo.precise.server.preciseData.PropertySetType;
import com.wgo.precise.server.preciseData.PropertyValueType;
import com.wgo.precise.server.preciseData.RefType;
import com.wgo.precise.server.preciseData.RequirementType;
import com.wgo.precise.server.preciseData.StakeholderType;

public class ImportPrecise {
	private static final transient Logger log = Logger.getLogger(ImportPrecise.class);
	private Map<Long,Requirement> requirements;
	private Map<String,Stakeholder> stakeholders;
	private RequirementSystem requirementSystem;
	private PersistService persistService;
	private boolean isValidate = true;
	
	
	public void persist(com.wgo.precise.server.preciseData.RequirementSystemDocument.RequirementSystem requirementSystemDoc){
		if (isValidate) {
			ExportPrecise.validateDoc(requirementSystemDoc);
		}
		persistService.createSchema();
		RequirementSystem requirementSystem = readDocument(requirementSystemDoc);
		RequirementSystem persistedRequirementSystem = persistService.getInstance(requirementSystem.getClass(), new Long(-1));
		((RequirementSystemImpl)persistedRequirementSystem).setProjectChildren(requirementSystem.getProjectChildren());
		((RequirementSystemImpl)persistedRequirementSystem).setStakeholders(requirementSystem.getStakeholders());
		((RequirementSystemImpl)persistedRequirementSystem).setPropertySets(requirementSystem.getPropertySets());
		persistService.saveOrUpdate(persistedRequirementSystem);
	}
	
	public RequirementSystem readDocument(com.wgo.precise.server.preciseData.RequirementSystemDocument.RequirementSystem requirementSystemDoc) {
		requirements = new HashMap<Long,Requirement>();
		requirementSystem = (RequirementSystem) createServerInstance(RequirementSystem.class);
		importStakeholders(requirementSystemDoc.getStakeholderArray(),requirementSystem);
		importPropertySets(requirementSystemDoc);
		importProjects(requirementSystemDoc.getProjectArray(), requirementSystem);
		setupLinkedRequirements(requirementSystemDoc,requirementSystem);
		return requirementSystem;
	}

	private void importPropertySets(com.wgo.precise.server.preciseData.RequirementSystemDocument.RequirementSystem requirementSystemDoc) {
		for (PropertySetType propertySetDoc : requirementSystemDoc.getPropertySetArray()) {
			PropertySetImpl propertySet = (PropertySetImpl)createServerInstance(PropertySet.class);
			propertySet.setName(str(propertySetDoc.getName()));
			propertySet.setDescription(str(propertySetDoc.getDescription()));
			if (null != propertySetDoc.getLastUpdatedDate()) {
				propertySet.setUpdatedDate(propertySetDoc.getLastUpdatedDate().getTime());
			}
			for (PropertyDefType propertyDefDoc : propertySetDoc.getPropertyDefArray()) {
				PropertyDefImpl propertyDef = (PropertyDefImpl)createServerInstance(PropertyDef.class);
				propertyDef.setName(str(propertyDefDoc.getName()));
				propertyDef.setDescription(str(propertyDefDoc.getDescription()));
				propertyDef.setDefaultValue(str(propertyDefDoc.getDefaultValue()));
				propertyDef.setPropertyTypeEnum(Enum.valueOf(PropertyTypeEnumImpl.class,propertyDefDoc.getTypeClass()));
				propertySet.addPropertyDef(propertyDef);
			}
			requirementSystem.addPropertySet(propertySet);
		}
	}

	private static String str(String str){
		if (null == str) {
			return "";
		} else {
			return str.trim();
		}
	}
	
	private void importProjects(ProjectType[] projectDocs, ProjectParentable projectParent) {
		for (ProjectType projectDoc : projectDocs) {
			Project project = importProject(projectDoc);
			projectParent.addProjectChild(project);
		}
	}

	private Project importProject(ProjectType projDoc) {
		ProjectImpl project = (ProjectImpl)createServerInstance(Project.class);
		project.setName(str(projDoc.getName()));
		project.setDescription(str(projDoc.getDescription()));
		importProjects(projDoc.getProjectChildArray(), project);
		importCategories(projDoc.getCategoryChildArray(),project);
		return project;
	}

	private void importStakeholders(StakeholderType[] stakeholderDocs, RequirementSystem requirementSystem) {
		stakeholders = new HashMap<String,Stakeholder>();
		for (StakeholderType stakeholderDoc : stakeholderDocs) {
			Stakeholder stakeholder = (StakeholderImpl)createServerInstance(Stakeholder.class);
			stakeholder.setFirstName(str(stakeholderDoc.getFirstName()));
			stakeholder.setLastName(str(stakeholderDoc.getLastName()));
			stakeholder.setDescription(str(stakeholderDoc.getDescription()));
			requirementSystem.addStakeholder(stakeholder);
			stakeholders.put(stakeholderDoc.getStakeholderId(),stakeholder);
		}
	}


	private void importCategories(CategoryType[] categoryDocs, CategoryParentable catParent) {
		for (CategoryType categoryDoc : categoryDocs) {
			Category category = importCategory(categoryDoc);
			catParent.addCategoryChild(category);
		}
	}

	private Category importCategory(CategoryType catDoc) {
		Category category = (CategoryImpl)createServerInstance(Category.class);
		category.setName(str(catDoc.getName()));
		category.setDescription(str(catDoc.getDescription()));
		for (RefType propSetRef : catDoc.getPropertySetArray()) {
			PropertySet propSet = findPropertySet(propSetRef);
			category.addPropertySet(propSet);
		}
		for (RequirementType reqDoc : catDoc.getRequirementArray()) {
			RequirementImpl req = (RequirementImpl)createServerInstance(Requirement.class);
			importRequirement(reqDoc, req);
			requirements.put(req.getBusinessId(),req);
			category.addRequirement(req);
		}
		importCategories(catDoc.getCategoryChildArray(),category);
		return category;
	}

	private void importRequirement(RequirementType reqDoc, RequirementImpl req) {
		req.setBusinessId(reqDoc.getRequirementId());
		req.setName(reqDoc.getName());
		req.setDescription(reqDoc.getDescription());
		for (RefType stakeholderRef : reqDoc.getStakeholderArray()) {
			Stakeholder stakeholder = findStakeholder(stakeholderRef);
			req.addStakeholder(stakeholder);
		}
		for (PropertyValueType propValDoc : reqDoc.getPropertyValueArray()) {
			PropertyValueImpl propertyValue = (PropertyValueImpl)createServerInstance(PropertyValue.class);
			propertyValue.setValue(str(propValDoc.getValue()));
			propertyValue.setPropertyDef(findPropertyDef(propValDoc.getPropertyDef()));
			req.putPropertyValue(propertyValue);
		}
		req.setAssignedTo(findStakeholder(reqDoc.getAssignedTo()));
		req.setReporterOrOwner(findStakeholder(reqDoc.getReporterOrOwner()));
		req.setPriority(Integer.parseInt(reqDoc.getPriority()));
		req.setStatus(Integer.parseInt(reqDoc.getStatus()));
		req.setVersion(reqDoc.getVersion());
		req.setLastUpdatedDate(reqDoc.getLastUpdatedDate().getTime());
		req.setSubmittedDate(reqDoc.getSubmittedDate().getTime());
	}

	private PropertyDef findPropertyDef(RefType propertyDefRef) {
		for (PropertySet propertySet : requirementSystem.getPropertySets()) {
			for (PropertyDef propDef : propertySet.getPropertyDefs()) {
				if (propertyDefRef.getRef().equals(getPropDefRef(propDef))) {
					return propDef;
				}
			}
		}
		return null;
	}

	private Stakeholder findStakeholder(RefType stakeholderRef) {
		return stakeholders.get(stakeholderRef.getRef());
	}

	private PropertySet findPropertySet(RefType propSetRef) {
		for (PropertySet propertySet : requirementSystem.getPropertySets()) {
			if (propSetRef.getRef().equals(getPropSetRef(propertySet))) {
				return propertySet;
			}
		}
		return null;
	}

	private String getPropDefRef(PropertyDef propertyDef) {
		return str(propertyDef.getName());
	}

	private String getPropSetRef(PropertySet propertySet) {
		return str(propertySet.getName());
	}

	private void setupLinkedRequirements(com.wgo.precise.server.preciseData.RequirementSystemDocument.RequirementSystem rsDoc,RequirementSystem requirementSystem) {
		for (ProjectType projectDoc : rsDoc.getProjectArray()) {
			for (CategoryType categoryDoc : projectDoc.getCategoryChildArray()) {
				for (RequirementType reqDoc : categoryDoc.getRequirementArray()) {
					Requirement req = requirements.get(reqDoc.getRequirementId());
					for (RefType linkedReqRef : reqDoc.getLinkedRequirementArray()) {
						Long reqId = Long.parseLong(linkedReqRef.getRef());
						req.addLinkedToRequirement(requirements.get(reqId));
					}
				}
			}
		}
	}

	public void setPersistService(PersistService persistService) {
		this.persistService = persistService;
	}

	private static Object createServerInstance(Class api){
		Class serverClass = DomainModelTypeNamingConvention.loadServerClass(api);
		try {
			return serverClass.newInstance();
		} catch (Exception e) {
			log.error("Unable to instantiate serverclass of api class: " + api.getName() );
		}
		return null;
	}

	public boolean isValidate() {
		return isValidate;
	}

	public void setValidate(boolean isValidate) {
		this.isValidate = isValidate;
	}
	
	
}
